#ifndef DWGREADER_H
#define DWGREADER_H

#include <osgDB/ReaderWriter>
#include <osgText/Font>
#include "scene.h"
#include "Utils.h"
// Forward declarations for libredwg
extern "C"
{
#include "dwg.h"
#include "bits.h"
}

class DWGReader : public osgDB::ReaderWriter
{
public:
    DWGReader();
    virtual ~DWGReader() {}

    virtual const char *className() const { return "DWG Reader"; }
    virtual bool acceptsExtension(const std::string &extension) const;
    virtual ReadResult readNode(const std::string &fileName, const Options *options) const;

protected:
    void convertEntityToScene(Dwg_Object *entity, Dwg_Data *dwg, DwgScene *dwgScene) const;

    void convertLineToScene(Dwg_Entity_LINE *line, const std::string &layerName,
                            const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertCircleToScene(Dwg_Entity_CIRCLE *circle, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertArcToScene(Dwg_Entity_ARC *arc, const std::string &layerName,
                           const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertTextToScene(Dwg_Entity_TEXT *text, const std::string &layerName,
                            const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertLWPolylineToScene(Dwg_Entity_LWPOLYLINE *polyline, const std::string &layerName,
                                  const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertPolyline2DToScene(Dwg_Object *entity, const std::string &layerName,
                                  const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;

    void convertPointToScene(Dwg_Entity_POINT *point, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertSolidToScene(Dwg_Entity_SOLID *solid, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convert3DFaceToScene(Dwg_Entity__3DFACE *face, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertPolylinePFaceToScene(Dwg_Object *entity, const std::string &layerName,
                                     const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertInsertToScene(Dwg_Entity_INSERT *insert, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertAttribToScene(Dwg_Entity_ATTRIB *attrib, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertMTextToScene(Dwg_Entity_MTEXT *mtext, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertHatchToScene(Dwg_Entity_HATCH *hatch, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convert3DSolidToScene(Dwg_Entity__3DSOLID *solid, const std::string &layerName,
                               const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertRegionToScene(Dwg_Entity_REGION *region, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertDimensionLinearToScene(Dwg_Entity_DIMENSION_LINEAR *dim, const std::string &layerName,
                                       const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertDimensionAlignedToScene(Dwg_Entity_DIMENSION_ALIGNED *dim, const std::string &layerName,
                                        const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertDimensionAng2LnToScene(Dwg_Entity_DIMENSION_ANG2LN *dim, const std::string &layerName,
                                       const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertDimensionOrdinateToScene(Dwg_Entity_DIMENSION_ORDINATE *dim, const std::string &layerName,
                                         const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertSplineToScene(Dwg_Entity_SPLINE *spline, const std::string &layerName,
                              const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertXLineToScene(Dwg_Entity_XLINE *xline, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertRayToScene(Dwg_Entity_RAY *ray, const std::string &layerName,
                           const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertPolyline3DToScene(Dwg_Object *entity, const std::string &layerName,
                                  const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertEllipseToScene(Dwg_Entity_ELLIPSE *ellipse, const std::string &layerName,
                               const osg::Vec4 &color, DwgScene *dwgScene) const;
    void convertMLineToScene(Dwg_Entity_MLINE *mline, const std::string &layerName,
                             const osg::Vec4 &color, DwgScene *dwgScene, Dwg_Data *dwg) const;
    void convertToleranceToScene(Dwg_Entity_TOLERANCE *tolerance, const std::string &layerName,
                                 const osg::Vec4 &color, DwgScene *dwgScene) const;

    osg::Vec4 convertColor(Dwg_Object_Entity *ent, Dwg_Data *dwg) const;

private:
    osg::ref_ptr<osgText::Font> font_;
    static std::vector<osg::Vec3d>
    tessellate_fit_catmull(const Dwg_Entity_SPLINE &s, int subdiv = 4)
    {
        std::vector<osg::Vec3d> in;
        in.reserve(s.num_fit_pts);
        for (size_t i = 0; i < s.num_fit_pts; ++i)
            in.emplace_back(s.fit_pts[i].x, s.fit_pts[i].y, s.fit_pts[i].z);

        bool closed = s.closed_b;
        std::vector<osg::Vec3d> inP = pad_catmull(in, closed);
        int n = (int)inP.size();
        std::vector<osg::Vec3d> out;

        int segs = 1 << subdiv;
        for (int i = 1; i < n - 2; ++i)
        {
            osg::Vec3d p0 = inP[i - 1];
            osg::Vec3d p1 = inP[i];
            osg::Vec3d p2 = inP[i + 1];
            osg::Vec3d p3 = inP[i + 2];
            for (int k = 0; k <= segs; ++k)
            {
                double t = double(k) / segs;
                double t2 = t * t;
                double t3 = t2 * t;
                osg::Vec3d pos = (p0 * (-t3 + 2 * t2 - t) +
                                  p1 * (3 * t3 - 5 * t2 + 2) +
                                  p2 * (-3 * t3 + 4 * t2 + t) +
                                  p3 * (t3 - t2)) *
                                 0.5;
                out.push_back(pos);
            }
        }
        return out;
    }

    static std::vector<osg::Vec3d>
    tessellate_nurbs(const Dwg_Entity_SPLINE &s,
                     const double *U,
                     int uCount,
                     int samples = 400)
    {
        std::vector<osg::Vec3d> pts;
        int p = s.degree;
        double u0 = U[p];
        double u1 = U[uCount - p - 1];
        double du = (u1 - u0) / samples;
        for (int i = 0; i <= samples; ++i)
        {
            double u = u0 + du * i;
            pts.push_back(eval_nurbs(s, U, uCount, u));
        }
        return pts;
    }

    static osg::Vec3d eval_nurbs(const Dwg_Entity_SPLINE &s,
                                 const double *U,
                                 int uCount,
                                 double u)
    {
        int p = s.degree;
        int n = s.num_ctrl_pts - 1;
        int k = -1;
        for (int i = p; i <= n; ++i)
            if (u >= U[i] && u < U[i + 1])
            {
                k = i;
                break;
            }
        if (k < 0)
            k = n;

        std::vector<osg::Vec3d> Pw(p + 1);
        std::vector<double> Ww(p + 1);
        for (int j = 0; j <= p; ++j)
        {
            int idx = k - p + j;
            double w = (s.weighted || s.rational) ? s.ctrl_pts[idx].w : 1.0;
            Pw[j] = osg::Vec3d(s.ctrl_pts[idx].x, s.ctrl_pts[idx].y,
                               s.ctrl_pts[idx].z) *
                    w;
            Ww[j] = w;
        }
        for (int r = 1; r <= p; ++r)
            for (int j = p; j >= r; --j)
            {
                double alpha = (u - U[k - p + j]) /
                               (U[k + j - r + 1] - U[k - p + j]);
                Pw[j] = Pw[j] * alpha + Pw[j - 1] * (1.0 - alpha);
                Ww[j] = Ww[j] * alpha + Ww[j - 1] * (1.0 - alpha);
            }
        double w = Ww[p];
        return osg::Vec3d(Pw[p].x() / w, Pw[p].y() / w, Pw[p].z() / w);
    }
};

#endif // DWGREADER_H